package net.entelijan.cobean.table;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.AbstractButton;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.RowFilter;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.JTextComponent;

import net.entelijan.cobean.core.ColumnDesc;
import net.entelijan.cobean.core.IInitializer;
import net.entelijan.cobean.core.IMultiSelectionListCobean;
import net.entelijan.cobean.core.SelectionMode;
import net.entelijan.cobean.core.TextComponentActionMode;
import net.entelijan.cobean.core.impl.AbstractComponentBean;
import net.entelijan.cobean.util.CobeanUtil;
import net.entelijan.cobean.util.Interval;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DefaultTableInitializer<T> implements
		IInitializer<IMultiSelectionListCobean<T>> {

	private static Log log = LogFactory.getLog(DefaultTableInitializer.class);

	protected SelectionMode selectionMode = SelectionMode.SINGLE;

	protected List<ColumnDesc> columnDescs;

	protected TextComponentActionMode filterActionMode = TextComponentActionMode.ON_KEY_STROKE;

	protected JTextComponent coFilterInput;

	protected AbstractButton coFilterButton;

	protected JTable coTable;

	public DefaultTableInitializer() {
		super();
	}

	public IMultiSelectionListCobean<T> initialize() {
		final TableRowSorter<AbstractBeansTableModel<T>> sorter = new TableRowSorter<AbstractBeansTableModel<T>>();
		final int[] filterIndizes = createFilterIndizes();
		if (coFilterInput != null) {
			if (this.filterActionMode == TextComponentActionMode.ON_ACTION
					&& coFilterButton == null) {
				throw new IllegalStateException(
						"You must define a filter button if you want to use the filtre method "
								+ TextComponentActionMode.ON_ACTION);
			}
			if (this.filterActionMode == TextComponentActionMode.ON_ACTION) {
				coFilterButton.addActionListener(new ActionListener() {

					@Override
					public void actionPerformed(ActionEvent e) {
						sorter.setRowFilter(RowFilter.regexFilter(coFilterInput
								.getText(), filterIndizes));
					}
				});
			} else {
				if (filterIndizes.length == 0) {
					log
							.warn("No filterable column was defined. Filter is disabled");
					coFilterInput.setEnabled(false);
				} else {
					switch (this.filterActionMode) {
					case ON_FOCUS_LOST:
						coFilterInput.addFocusListener(new FocusAdapter() {

							@Override
							public void focusLost(FocusEvent e) {
								sorter
										.setRowFilter(RowFilter.regexFilter(
												coFilterInput.getText(),
												filterIndizes));
							}

						});
						break;

					case ON_KEY_STROKE:
						coFilterInput.addKeyListener(new KeyAdapter() {
							@Override
							public void keyReleased(KeyEvent e) {
								sorter
										.setRowFilter(RowFilter.regexFilter(
												coFilterInput.getText(),
												filterIndizes));
							}
						});
						break;

					default:
						// Do not add listener for other modes

					}

				}

			}
		}
		ArrayList<T> emptyList = new ArrayList<T>();
		initTableInternal(sorter, emptyList);
		final ComponentBean re = new ComponentBean(sorter, emptyList);
		DefaultListSelectionModel selModel = new DefaultListSelectionModel() {

			private static final long serialVersionUID = 1L;

			@Override
			public void setSelectionInterval(int from, int to) {
				List<T> oldSel = re.getSelectedValue();
				List<Interval> selectionIntervals = CobeanUtil
						.createSelectionIntervals(re.getValue(), oldSel);
				super.setSelectionInterval(from, to);
				if (selectionIntervals.size() != 1
						|| !(selectionIntervals.get(0).getFrom() == from && selectionIntervals
								.get(0).getTo() == to)) {
					List<T> newSel = re.getSelectedValue();
					log.debug("[setSelectionInterval] " + oldSel + "->"
							+ newSel);
					re.fireSelectionChanged(oldSel, newSel);
				}
			}

			@Override
			public void addSelectionInterval(int index0, int index1) {
				List<T> oldSel = re.getSelectedValue();
				super.addSelectionInterval(index0, index1);
				List<T> newSel = re.getSelectedValue();
				log.debug("[addSelectionInterval] " + oldSel + "->" + newSel);
				re.fireSelectionChanged(oldSel, newSel);
			}

			@Override
			public void removeSelectionInterval(int index0, int index1) {
				List<T> oldSel = re.getSelectedValue();
				super.removeSelectionInterval(index0, index1);
				List<T> newSel = re.getSelectedValue();
				log
						.debug("[removeSelectionInterval] " + oldSel + "->"
								+ newSel);
				re.fireSelectionChanged(oldSel, newSel);
			}

		};
		selModel.addListSelectionListener(new ListSelectionListener() {

			@Override
			public void valueChanged(ListSelectionEvent e) {
				log.info("[valueChanged] " + e);
			}
		});
		coTable.setSelectionModel(selModel);
		return re;
	}

	private int[] createFilterIndizes() {
		ArrayList<Integer> reli = new ArrayList<Integer>();
		int i = 0;
		if (columnDescs != null) {
			for (ColumnDesc columnDesc : columnDescs) {
				if (columnDesc.isFilterable()) {
					reli.add(i);
				}
				i++;
			}
		}
		int j = 0;
		int[] re = new int[reli.size()];
		for (Integer index : reli) {
			re[j++] = index;
		}
		return re;
	}

	private void initTableInternal(
			TableRowSorter<AbstractBeansTableModel<T>> sorter,
			Collection<T> data) {
		if (this.selectionMode != null) {
			coTable.setRowSelectionAllowed(true);
			coTable
					.setSelectionMode(this.selectionMode
							.getSwingSelectionMode());
		} else {
			coTable.setRowSelectionAllowed(false);
		}
		AbstractBeansTableModel<T> beansTableModel = createBeansTableModel(
				data, coTable, sorter);
		sorter.setModel(beansTableModel);
		initSorter(sorter);
		coTable.setModel(beansTableModel);
		coTable.setColumnModel(createTableColumnModel(coTable));
		coTable.setRowSorter(sorter);
	}

	private void initSorter(TableRowSorter<AbstractBeansTableModel<T>> sorter) {
		if (columnDescs != null) {
			int i = 0;
			for (ColumnDesc cd : columnDescs) {
				sorter.setSortable(i, cd.isSortable());
				if (cd.getComparator() != null) {
					sorter.setComparator(i, cd.getComparator());
				}
				i++;
			}
		}
	}

	private TableColumnModel createTableColumnModel(JTable table) {
		JTableHeader header = table.getTableHeader();
		DefaultTableColumnModel re = new SwtWorkaroundTableColumnModel();
		if (columnDescs != null && columnDescs.size() > 0) {
			int i = 0;
			for (ColumnDesc desc : columnDescs) {
				re.addColumn(createTableColumn(desc, i++, header));
			}
		} else {
			throw new IllegalStateException("No columns defined");
		}
		return re;
	}

	private TableColumn createTableColumn(final ColumnDesc desc, int index,
			JTableHeader header) {
		final TableColumn re = new TableColumn(index);
		if (desc.getWidth() >= 0) {
			re.setPreferredWidth(desc.getWidth());
		}
		re.setHeaderValue(desc.getHeaderName());
		if (desc.getConverter() != null) {
			re.setCellRenderer(new DefaultTableCellRenderer() {

				private static final long serialVersionUID = 1L;

				@Override
				public Component getTableCellRendererComponent(JTable table,
						Object value, boolean isSelected, boolean hasFocus,
						int row, int column) {
					final Component comp = super.getTableCellRendererComponent(
							table, value, isSelected, hasFocus, row, column);
					if (comp instanceof JLabel) {
						((JLabel) comp).setText(desc.getConverter()
								.objectToString(value));
					}
					return comp;
				}

			});
		}
		return re;
	}

	private AbstractBeansTableModel<T> createBeansTableModel(
			Collection<T> beans, final JTable table,
			final TableRowSorter<AbstractBeansTableModel<T>> sorter) {
		return new AbstractBeansTableModel<T>(beans, columnDescs, table) {

			private static final long serialVersionUID = 1L;

			public List<T> getSelection() {
				ArrayList<T> re = new ArrayList<T>();
				int[] selectedRowIndices = table.getSelectedRows();
				for (int index : selectedRowIndices) {
					int mindex = sorter.convertRowIndexToModel(index);
					re.add(getBean(mindex));
				}
				return re;
			}

		};
	}

	public class ComponentBean extends AbstractComponentBean implements
			IMultiSelectionListCobean<T> {
		private final TableRowSorter<AbstractBeansTableModel<T>> sorter;
		private List<T> beans;

		private ComponentBean(
				TableRowSorter<AbstractBeansTableModel<T>> sorter, List<T> beans) {
			this.sorter = sorter;
			this.beans = beans;
		}

		public void fireSelectionChanged(Object oldValue, Object newValue) {
			this.propertyChangeSupport.firePropertyChange("selectedValue",
					oldValue, newValue);
		}

		public List<T> getValue() {
			return this.beans;
		}

		public void setValue(List<T> value) {
			List<T> oldValue = getBeans();
			this.beans = value;
			initTableInternal(sorter, (Collection<T>) value);
			initSorter(sorter);
			coTable.repaint();
			this.propertyChangeSupport.firePropertyChange("value", oldValue,
					value);
		}

		private List<T> getBeans() {
			List<T> re = this.beans;
			if (re != null && re.size() == 0) {
				re = null;
			}
			return re;
		}

		public List<T> getSelectedValue() {
			ISelectionProvider<T> sp = getSelectionProvider();
			List<T> re = sp.getSelection();
			if (re != null && re.size() == 0) {
				re = null;
			}
			return re;
		}

		@SuppressWarnings("unchecked")
		private ISelectionProvider<T> getSelectionProvider() {
			return (ISelectionProvider<T>) coTable.getModel();
		}

		public void setSelectedValue(List<T> data) {
			if (data == null) {
				List<T> oldSel = getSelectedValue();
				if (oldSel != null) {
					coTable.getSelectionModel().clearSelection();
					fireSelectionChanged(oldSel, null);
				}
			} else {
				List<Interval> oldInts = CobeanUtil.createSelectionIntervals(
						beans, getSelectedValue());
				List<Interval> ints = CobeanUtil.createSelectionIntervals(
						beans, data);
				log.debug("[setSelectedValue] " + oldInts + "->" + ints);
				if (!equalIntervals(oldInts, ints)) {
					for (Interval inv : ints) {
						coTable.getSelectionModel().setSelectionInterval(
								inv.getFrom(), inv.getTo());
						// Select the first column as the default table model
						// works only correct if a column is selected.
						coTable.getColumnModel().getSelectionModel()
								.setSelectionInterval(0, 0);
					}
				}
			}
		}

		private boolean equalIntervals(List<Interval> ints1,
				List<Interval> ints2) {
			boolean re;
			if (ints1.size() != ints2.size()) {
				re = false;
			} else {
				re = true;
				for (Interval int1 : ints1) {
					if (!ints2.contains(int1)) {
						re = false;
						break;
					}
				}
			}
			return re;
		}

	}

	public net.entelijan.cobean.core.SelectionMode getSelectionMode() {
		return selectionMode;
	}

	public void setSelectionMode(
			net.entelijan.cobean.core.SelectionMode selectionMode) {
		this.selectionMode = selectionMode;
	}

	public List<ColumnDesc> getColumnDescs() {
		return columnDescs;
	}

	public void setColumnDescs(List<ColumnDesc> columnDescs) {
		this.columnDescs = columnDescs;
	}

	public TextComponentActionMode getFilterActionMode() {
		return filterActionMode;
	}

	public void setFilterActionMode(TextComponentActionMode filterActionMode) {
		this.filterActionMode = filterActionMode;
	}

	public JTextComponent getCoFilterInput() {
		return coFilterInput;
	}

	public void setCoFilterInput(JTextComponent coFilterInput) {
		this.coFilterInput = coFilterInput;
	}

	public JTable getCoTable() {
		return coTable;
	}

	public void setCoTable(JTable coTable) {
		this.coTable = coTable;
	}

	public AbstractButton getCoFilterButton() {
		return coFilterButton;
	}

	public void setCoFilterButton(AbstractButton coFilterButton) {
		this.coFilterButton = coFilterButton;
	}

}
